import { CanceledError } from 'axios'
import imageCompression from 'browser-image-compression'
import { computed, reactive, readonly, ref } from 'vue'
import { defineStore, storeToRefs } from 'pinia'
import { useAccountStore } from '@/store/account'
import { $t } from '@/i18n'
import { FEED_PERMISION, INTERESTED_IN } from '@/constant'
import { MEDIA_TYPE, PREVIEW, RATIO_TYPE, UPLOAD_STATUS } from '@/constant/publish'
import uploadImage from '@/http/upload/uploadImage'
import uploadMultipart from '@/http/upload/uploadMultipart'
import uploadVideo from '@/http/upload/uploadVideo'

// 上傳的服務類型
// 請參照 constant/publish.js
let service = null

// 使用者所選的檔案
// https://developer.mozilla.org/en-US/docs/Web/API/File
let fileList = null

// 上傳過程中被中段的 callback
const cnacelUploadFnList = []

const compressOptions = {
  maxSizeMB: 3,
  maxWidthOrHeight: 1920,
  useWebWorker: true,
}

const VIDEO_SIZE_THRESHOLD = 50 * 1024 * 1024

/**
 * 1. 使用者選擇檔案後會呼叫 setFile(fils, service)
 *
 * 2. UploadStatus.vue 有 watch(onFileInput, () => {})
 *    會得知有新的檔案被選擇
 *    會執行 startUpload 開始做檔案處理(處理過程相關資料會放在 uploadFiles)然後上傳
 *
 * 3. 若使用者離開上傳頁面(透過取消按鈕或上一頁)就會呼叫 clear()
 */
export const usePublishStore = defineStore('publish', () => {
  const idMaker = (function* () {
    let id = 0
    while (true) {
      yield id++
    }
  })()

  const accountStore = useAccountStore()
  const { userData } = storeToRefs(accountStore)

  const onFileInput = ref(null)

  const publishParams = reactive({
    // 新增的話為null，修改的話為該帖子id
    id: null,

    type: null,
    shape: null,
    content: '',
    tags: [],
    attribute: [],
    visible: FEED_PERMISION.ALL,
    money: null,
    postTime: null,
    openPreview: PREVIEW.CLOSE,

    // type 是圖片
    imgPath: null,
    imgWidth: null,
    imgHeight: null,

    // type 是影片
    videoPath: null,
    videoTime: null,

    // 編輯舊貼文時影片可能會有處理切片時的狀態
    slice: null,
  })

  // 陣列中元素會是 video 和 image
  // 不會 video 和 image 交錯出現
  // video 的話只會有一個
  // image 可能會有多個
  //
  // video
  // {
  //   id, file, progress, progressMsg, status, failMsg, // from setFile
  //   objectUrl,                                        // from videoObjectUrlExtract
  //   width, height, duration,                          // from videoPropertyExtract
  //   url,                                              // from videoUpload
  // }
  //
  // image
  // {
  //   id, file, progress, progressMsg, status, failMsg, // from setFile
  //   compressedFile,                                   // from imageCompress
  //   width, height, result,                            // from imagePropertyExtract
  //   url,                                              // from imageUpload
  // }
  const uploadFiles = ref([])
  const noUploadFiles = computed(() => uploadFiles.value.length === 0)

  const isCreate = computed(() => publishParams.id === null)
  const isUpdate = computed(() => publishParams.id !== null)

  const isVideo = computed(() => publishParams.type === MEDIA_TYPE.VIDEO)
  const isImage = computed(() => publishParams.type === MEDIA_TYPE.IMAGE)
  const isHorizontal = computed(() => publishParams.shape === RATIO_TYPE.HORIZONTAL)
  const isVertical = computed(() => publishParams.shape === RATIO_TYPE.VERTICAL)

  const publishTimeOpen = computed({
    get() {
      return publishParams.postTime !== null
    },
    set(v) {
      publishParams.postTime = v ? new Date() : null
    },
  })

  function setFile(files, serviceVal = null) {
    checkFileType(files)

    service = serviceVal
    fileList = files

    for (const file of files) {
      pushUploadFile(file)
    }

    onFileInput.value = Date.now()

    // 重新將 attribute 的預設值調成 accountStore.userData 中的 interested_type
    const userDefaultAttribute = userData.value.interested_type
    publishParams.attribute = userDefaultAttribute.split(',').map((a) => parseInt(a))
  }

  function addVideoFile(file, videoRef) {
    if (getMediaType(file.type) !== MEDIA_TYPE.VIDEO) {
      throw new Error($t('content.onlyAddVideo'))
    }

    if (uploadFiles.value.length > 0) {
      throw new Error($t('content.videoLimitOne'))
    }

    pushUploadFile(file)

    startUpload(videoRef)
  }

  function addImageFile(files) {
    for (const file of files) {
      if (getMediaType(file.type) !== MEDIA_TYPE.IMAGE) {
        throw new Error($t('content.onlyAddImage'))
      }
    }

    for (const file of files) {
      pushUploadFile(file)
    }

    startUpload()
  }

  function pushUploadFile(file) {
    uploadFiles.value.push({
      id: idMaker.next().value,
      file,
      progress: 0,
      progressMsg: null,
      status: UPLOAD_STATUS.PENDING,
      failMsg: null,
    })
  }

  function pushSavedFile(url, width, height) {
    uploadFiles.value.push({
      id: idMaker.next().value,
      progress: 1,
      progressMsg: null,
      status: UPLOAD_STATUS.SAVE,
      failMsg: null,
      url,
      width,
      height,
    })
  }

  function checkFileType(files) {
    const firstFileType = files[0].type
    const feedType = getMediaType(firstFileType)

    if (feedType === MEDIA_TYPE.VIDEO && files.length > 1) {
      throw new Error($t('content.videoLimitOne'))
    }

    if (feedType === MEDIA_TYPE.IMAGE) {
      for (let i = 1; i < files.length; i++) {
        if (getMediaType(files[i].type) !== MEDIA_TYPE.IMAGE) {
          throw new Error($t('content.imageVideoNotMix'))
        }
      }
    }

    publishParams.type = feedType
  }

  function getMediaType(type) {
    switch (type) {
      case 'video/mp4':
      case 'video/quicktime':
      case 'video/x-quicktime':
      case 'video/mov':
      case 'video/x-mov':
      case 'video/avi':
        return MEDIA_TYPE.VIDEO
      case 'image/jpg':
      case 'image/jpeg':
      case 'image/png':
      case 'image/gif':
        return MEDIA_TYPE.IMAGE
      default:
        throw new Error($t('content.uploadVideoLimit.format', { format: type }))
    }
  }

  // 16(height) / 9(width) = 1.7777(垂直)
  // 9(height) / 16(width) = 0.5625(水平)
  function setVideoProperty(width, height, duration) {
    const ratio = height / width
    publishParams.shape = ratio < 1 ? RATIO_TYPE.HORIZONTAL : RATIO_TYPE.VERTICAL
    publishParams.videoTime = duration
  }

  function setPublishParams({
    id,
    type,
    shape,
    content,
    tags,
    attribute,
    visible,
    money,
    postTime = null,
    imgPath,
    imgWidth,
    imgHeight,
    imgMore,
    videoPath,
    videoTime,
    slice,
  }) {
    publishParams.id = id
    publishParams.type = type
    publishParams.shape = shape
    publishParams.content = content
    publishParams.tags = tags ? tags.split(',').filter((t, i) => i !== 0 || t !== '') : []
    publishParams.attribute = attribute ? attribute.split(',').map((a) => parseInt(a)) : [INTERESTED_IN.STRAIGHT]
    publishParams.visible = visible
    publishParams.money = money
    publishParams.postTime = postTime
    publishParams.imgPath = imgPath
    publishParams.videoPath = videoPath
    publishParams.videoTime = videoTime
    publishParams.slice = slice

    if (type === MEDIA_TYPE.VIDEO) {
      pushSavedFile(imgPath)
    }

    if (type === MEDIA_TYPE.IMAGE) {
      pushSavedFile(imgPath, imgWidth, imgHeight)
      if (imgMore) {
        for (const { url, width, height } of imgMore) {
          pushSavedFile(url, width, height)
        }
      }
    }
  }

  function getUploadPayload() {
    return {
      service,
      fileList,
    }
  }

  async function startUpload(videoRef) {
    if (isVideo.value) {
      await preprocessing(uploadFiles.value[0])
        .then(videoObjectUrlExtract)
        .then(videoPropertyExtract(videoRef, setVideoProperty))
        .then(videoUpload(onCancelUpload))
        .then((uploadFile) => (publishParams.videoPath = uploadFile.url))
    }

    if (isImage.value) {
      await Promise.all(
        uploadFiles.value
          .filter((uploadFile) => uploadFile.status === UPLOAD_STATUS.PENDING)
          .map((uploadFile) =>
            preprocessing(uploadFile).then(imageCompress).then(imagePropertyExtract).then(imageUpload),
          ),
      )
    }
  }

  function removeUploadFile(id) {
    const index = uploadFiles.value.findIndex((uf) => uf.id === id)
    if (index !== -1) {
      uploadFiles.value.splice(index, 1)
    }
  }

  function reUploadFile(id) {
    const index = uploadFiles.value.findIndex((uf) => uf.id === id)
    if (index !== -1) {
      const uploadFile = uploadFiles.value[index]
      if (uploadFile.status === UPLOAD_STATUS.FAIL) {
        uploadFile.progress = 0
        if (isVideo.value) {
          preprocessing(uploadFile)
            .then(videoUpload(onCancelUpload))
            .then((uploadFile) => (publishParams.videoPath = uploadFile.url))
        }
        if (isImage.value) {
          preprocessing(uploadFile).then(imageCompress).then(imagePropertyExtract).then(imageUpload)
        }
      }
    }
  }

  function onCancelUpload(fn) {
    cnacelUploadFnList.push(fn)
  }

  function cancelUpload() {
    cnacelUploadFnList.forEach((cancel) => cancel())
  }

  function clear() {
    onFileInput.value = null

    uploadFiles.value = []

    publishParams.id = null
    publishParams.type = null
    publishParams.shape = null
    publishParams.content = ''
    publishParams.tags = []
    publishParams.attribute = []
    publishParams.visible = FEED_PERMISION.ALL
    publishParams.money = ''
    publishParams.postTime = null
    publishParams.openPreview = PREVIEW.CLOSE
    publishParams.imgPath = null
    publishParams.imgWidth = null
    publishParams.imgHeight = null
    publishParams.videoPath = null
    publishParams.videoTime = null
    publishParams.slice = null

    service = null
    fileList = null
    cnacelUploadFnList.length = 0
  }

  return {
    onFileInput: readonly(onFileInput),

    uploadFiles,
    noUploadFiles,

    publishParams,
    isCreate,
    isUpdate,
    isVideo,
    isImage,
    isHorizontal,
    isVertical,
    publishTimeOpen,

    setFile,
    setPublishParams,
    getUploadPayload,
    startUpload,
    addVideoFile,
    addImageFile,
    removeUploadFile,
    reUploadFile,
    cancelUpload,
    clear,
  }
})

function preprocessing(uploadFile) {
  return new Promise((resolve, reject) => {
    uploadFile.status = UPLOAD_STATUS.PREPROCESSING
    resolve(uploadFile)
  })
}

function videoObjectUrlExtract(uploadFile) {
  return new Promise((resolve, reject) => {
    uploadFile.objectUrl = URL.createObjectURL(uploadFile.file)
    resolve(uploadFile)
  })
}

function videoPropertyExtract(videoRef, setVideoProperty) {
  return (uploadFile) =>
    new Promise((resolve, reject) => {
      const videoEle = videoRef.value
      videoEle.src = uploadFile.objectUrl
      videoEle.muted = true
      videoEle.onloadedmetadata = () => {
        const { videoWidth, videoHeight, duration } = videoEle
        // if (duration > VIDEO_SECOND_LIMIT) {
        //   console.log(`視頻長度${videoEle.duration}(秒)超過限制長度${VIDEO_SECOND_LIMIT}(秒)`)
        //   reject(new VideoLengthOverlimitError('videoOverlimit'))
        // } else {
        uploadFile.width = videoWidth
        uploadFile.height = videoHeight
        uploadFile.duration = duration
        setVideoProperty(videoWidth, videoHeight, duration)
        resolve(uploadFile)
        // }
      }
    })
}

function videoUpload(onCancel) {
  return (uploadFile) =>
    new Promise((resolve, reject) => {
      const uploader = uploadFile.file.size < VIDEO_SIZE_THRESHOLD ? uploadVideo : uploadMultipart
      uploadFile.status = UPLOAD_STATUS.UPLOADING
      uploader(
        uploadFile.file,
        (p, m) => {
          uploadFile.progress = p
          uploadFile.progressMsg = m
        },
        onCancel,
      )
        .then(({ publicUrl, uploadName }) => {
          uploadFile.status = UPLOAD_STATUS.DONE
          uploadFile.url = uploadName
          resolve(uploadFile)
        })
        .catch((err) => {
          if (err instanceof CanceledError) {
            return
          }
          uploadFile.status = UPLOAD_STATUS.FAIL
          uploadFile.failMsg = err.message
        })
    })
}

function imageCompress(uploadFile) {
  return new Promise((resolve, reject) => {
    imageCompression(uploadFile.file, compressOptions)
      .then((compressedFile) => {
        console.log(`image compressed size ${compressedFile.size / 1024 / 1024} MB`)
        uploadFile.compressedFile = compressedFile
        resolve(uploadFile)
      })
      .catch((err) => {
        uploadFile.status = UPLOAD_STATUS.FAIL
        uploadFile.failMsg = $t('content.processFail')
        console.error(err)
      })
  })
}

function imagePropertyExtract(uploadFile) {
  return new Promise((resolve, reject) => {
    const fr = new FileReader()
    fr.onload = function () {
      const temp = new Image()
      temp.src = fr.result
      temp.onload = () => {
        uploadFile.width = temp.width
        uploadFile.height = temp.height
        uploadFile.result = fr.result
        resolve(uploadFile)
      }
    }
    fr.onerror = function (evt) {
      uploadFile.status = UPLOAD_STATUS.FAIL
      uploadFile.failMsg = $t('content.processFail')
      console.error(evt)
    }
    fr.readAsDataURL(uploadFile.compressedFile)
  })
}

function imageUpload(uploadFile) {
  return new Promise((resolve, reject) => {
    uploadFile.status = UPLOAD_STATUS.UPLOADING
    uploadImage(uploadFile.compressedFile, (p) => (uploadFile.progress = p))
      .then((url) => {
        uploadFile.status = UPLOAD_STATUS.DONE
        uploadFile.url = url
      })
      .catch((err) => {
        uploadFile.status = UPLOAD_STATUS.FAIL
        uploadFile.failMsg = err.message
      })
  })
  // console.log('imageUpload', uploadFile)
}
